package com.millstein.service.impl;

import cn.hutool.core.collection.CollectionUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.millstein.context.UserAuthorization;
import com.millstein.enums.BizCodeEnum;
import com.millstein.enums.coupon.CategoryEnum;
import com.millstein.enums.coupon.CouponStateEnum;
import com.millstein.enums.coupon.PublishEnum;
import com.millstein.exception.BizException;
import com.millstein.model.CouponDO;
import com.millstein.mapper.CouponMapper;
import com.millstein.model.CouponRecordDO;
import com.millstein.model.LoginUser;
import com.millstein.request.NewUserCouponRequest;
import com.millstein.service.CouponRecordService;
import com.millstein.service.CouponService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.millstein.util.CommonUtils;
import com.millstein.vo.CouponVO;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.Date;
import java.util.List;
import java.util.stream.Collectors;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author millstein
 * @since 2021-09-14
 */
@Service
public class CouponServiceImpl extends ServiceImpl<CouponMapper, CouponDO> implements CouponService {

    @Autowired
    private CouponRecordService couponRecordService;

    @Autowired
    private RedissonClient redissonClient;

    /**
     * 分页条件查询优惠券信息
     *
     * @param page 当前页
     * @param size 每页记录数
     * @return
     */
    @Override
    public Page<CouponVO> pageCoupon(int page, int size) {
        Page<CouponDO> pageInfo = new Page<>(page, size);
        QueryWrapper<CouponDO> wrapper = new QueryWrapper<>();
        wrapper.eq("category", CategoryEnum.PROMOTION)
                .eq("publish", PublishEnum.PUBLISH);
        this.page(pageInfo, wrapper);

        List<CouponVO> voList = pageInfo.getRecords().stream().map(CouponVO::new).collect(Collectors.toList());

        Page<CouponVO> returnPage = new Page<>(page, size);
        BeanUtils.copyProperties(pageInfo, returnPage);
        returnPage.setRecords(voList);
        return returnPage;
    }

    /**
     * 领用优惠券
     *
     * @param id       优惠券id
     * @param category 优惠券类型
     */
    @Override
    @Transactional(rollbackFor = Exception.class)
    public void addCoupon(Long id, CategoryEnum category) {
        // 1.获取当前登录用户的信息
        LoginUser loginUser = UserAuthorization.getUserContext();

        CouponDO couponDO = null;

        // 2.加分布式锁。查询的优惠券信息和对优惠券的校验必须加锁，否则会出现超领现象
        RLock lock = redissonClient.getLock("lock:add_coupon:" + id);
        try {
            lock.lock();

            // 3.查询优惠券信息
            couponDO = getCouponInfo(id, category);

            // 4.检查优惠券能不能领用
            checkValidForCoupon(couponDO, loginUser);

        } finally {
            lock.unlock();
        }

        // 5.构建领用优惠券的领用记录
        CouponRecordDO couponRecordDO = buildCouponRecord(couponDO, loginUser);

        // 6.扣减优惠券数量并保存记录。扣减库存也需要加锁实现，但这里使用了mysql的sql语句实现了加锁的效果
        reduceStockAndInsertRecord(couponDO, couponRecordDO);

    }

    /**
     * 获取优惠券信息
     * @param id 优惠券id
     * @param category 优惠券类型
     * @return
     */
    private CouponDO getCouponInfo(Long id, CategoryEnum category) {
        QueryWrapper<CouponDO> wrapper = new QueryWrapper<>();
        wrapper.eq("id", id)
                .eq("category", category.name());
        CouponDO couponDO = this.getOne(wrapper);

        if (couponDO == null) {
            throw new BizException(BizCodeEnum.COUPON_NO_EXITS);
        }
        return couponDO;
    }

    /**
     * 判断该优惠券能否被领用
     * @param couponDO 优惠券信息
     * @param loginUser 当前登录的用户信息
     */
    private void checkValidForCoupon(CouponDO couponDO, LoginUser loginUser) {
        // 校验优惠券的状态
        if (!couponDO.getPublish().equals(PublishEnum.PUBLISH.name())) {
            throw new BizException(BizCodeEnum.COUPON_CONDITION_ERROR);
        }

        // 校验库存是否足够
        if (couponDO.getStock() <= 0) {
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
        }

        // 判断是否在领取时间范围
        long time = CommonUtils.getCurrentTimestamp();
        long start = couponDO.getStartTime().getTime();
        long end = couponDO.getEndTime().getTime();
        if(time < start || time > end) {
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_TIME);
        }

        // 判断用户的领用是否超过限制
        int recordNum = couponRecordService.count(new QueryWrapper<CouponRecordDO>()
                .eq("coupon_id", couponDO.getId())
                .eq("user_id", loginUser.getId()));
        if(recordNum >= couponDO.getUserLimit()) {
            throw new BizException(BizCodeEnum.COUPON_OUT_OF_LIMIT);
        }
    }

    /**
     * 构建领用优惠券的领用记录
     * @param couponDO 优惠券信息
     * @param loginUser 当前用户信息
     * @return
     */
    private CouponRecordDO buildCouponRecord(CouponDO couponDO, LoginUser loginUser) {
        CouponRecordDO couponRecordDO = new CouponRecordDO();
        BeanUtils.copyProperties(couponDO, couponRecordDO);
        couponRecordDO.setCreateTime(new Date());
        couponRecordDO.setUseState(CouponStateEnum.NEW.name());
        couponRecordDO.setUserId(loginUser.getId());
        couponRecordDO.setUserName(loginUser.getName());
        couponRecordDO.setCouponId(couponDO.getId());
        couponRecordDO.setId(null);
        return couponRecordDO;
    }

    /**
     * 扣减优惠券数量并保存记录
     * @param couponDO 优惠券信息
     * @param couponRecordDO 优惠券领用记录信息
     */
    private void reduceStockAndInsertRecord(CouponDO couponDO, CouponRecordDO couponRecordDO) {
        // 扣减库存
        int row = baseMapper.reduceStock(couponDO.getId());
        if (row == 0) {
            throw new BizException(BizCodeEnum.COUPON_NO_STOCK);
        }
        // 保存领用记录
        if (!couponRecordService.save(couponRecordDO)) {
            throw new BizException(BizCodeEnum.COUPON_GET_FAIL);
        }
    }

    /**
     * 发放新用户优惠券
     *
     * @param newUserCouponRequest 用户信息
     */
    @Override
    @Transactional
    public void addNewUserCoupon(NewUserCouponRequest newUserCouponRequest) {
        // 为了能重用addCoupon方法，这里要手动创建一个loginUser并放到threadLocal中去
        LoginUser loginUser = new LoginUser();
        loginUser.setId(newUserCouponRequest.getUserId());
        loginUser.setName(newUserCouponRequest.getName());
        UserAuthorization.setUserContext(loginUser);

        // 查询新用户的优惠券
        QueryWrapper<CouponDO> wrapper = new QueryWrapper<>();
        wrapper.eq("category", CategoryEnum.NEW_USER.name())
                .eq("publish", PublishEnum.PUBLISH);
        List<CouponDO> list = this.list(wrapper);

        // 发放新用户优惠券
        if (CollectionUtil.isEmpty(list)) {
            return;
        }
        for (CouponDO couponDO : list) {
            addCoupon(couponDO.getId(), CategoryEnum.NEW_USER);
        }
    }
}
